package net.apexes.wsonrpc.server.support;

import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.ServerWebSocket;
import io.vertx.core.http.WebSocketFrame;
import net.apexes.wsonrpc.core.WebSocketSession;
import net.apexes.wsonrpc.core.WsonrpcConfig;
import net.apexes.wsonrpc.server.PathAcceptor;
import net.apexes.wsonrpc.server.PathAcceptors;
import net.apexes.wsonrpc.server.WsonrpcCloseListener;
import net.apexes.wsonrpc.server.WsonrpcMessageListener;
import net.apexes.wsonrpc.server.WsonrpcOpenListener;
import net.apexes.wsonrpc.server.WsonrpcPingListener;
import net.apexes.wsonrpc.server.WsonrpcRequestInterceptor;
import net.apexes.wsonrpc.server.WsonrpcServer;
import net.apexes.wsonrpc.server.WsonrpcServerBase;

import java.io.IOException;
import java.nio.ByteBuffer;

/**
 * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
 */
public class VertxWsonrpcHandler implements Handler<ServerWebSocket> {
    
    private static final WebSocketFrame PING_FRAME = WebSocketFrame.pingFrame(Buffer.buffer());
    
    protected final WsonrpcServerBase serverBase;
    protected final PathAcceptor pathAcceptor;
    private WsonrpcPingListener pingListener;
    
    public VertxWsonrpcHandler(WsonrpcConfig config) {
        this(config, PathAcceptors.rootPath());
    }

    public VertxWsonrpcHandler(WsonrpcConfig config, PathAcceptor pathAcceptor) {
        this.serverBase = new WsonrpcServerBase(config);
        this.pathAcceptor = pathAcceptor;
    }

    public VertxWsonrpcHandler(WsonrpcServerBase serverBase) {
        this(serverBase, PathAcceptors.rootPath());
    }

    public VertxWsonrpcHandler(WsonrpcServerBase serverBase, PathAcceptor pathAcceptor) {
        this.serverBase = serverBase;
        this.pathAcceptor = pathAcceptor;
    }

    public WsonrpcServer getWsonrpcServer() {
        return serverBase;
    }

    public WsonrpcPingListener getPingListener() {
        return pingListener;
    }

    public void setPingListener(WsonrpcPingListener pingListener) {
        this.pingListener = pingListener;
    }

    public void setWsonrpcOpenListener(WsonrpcOpenListener listener) {
        serverBase.setWsonrpcOpenListener(listener);
    }

    public void setWsonrpcCloseListener(WsonrpcCloseListener listener) {
        serverBase.setWsonrpcCloseListener(listener);
    }

    public void setWsonrpcMessageListener(WsonrpcMessageListener listener) {
        serverBase.setWsonrpcMessageListener(listener);
    }

    public void setWsonrpcRequestInterceptor(WsonrpcRequestInterceptor interceptor) {
        serverBase.setWsonrpcRequestInterceptor(interceptor);
    }

    @Override
    public void handle(ServerWebSocket webSocket) {
        onConnect(webSocket);
    }

    protected void onConnect(ServerWebSocket webSocket) {
        if (pathAcceptor.accept(webSocket.path())) {
            onOpen(webSocket, new ServerWebSocketSessionAdapter(webSocket));
        } else {
            webSocket.reject();
        }
    }

    protected void onOpen(ServerWebSocket webSocket, WebSocketSession session) {
        serverBase.onOpen(session);
        webSocket.frameHandler(frame -> onFrame(session.getId(), frame));
        webSocket.closeHandler(v -> onClose(session.getId()));
    }

    protected void onFrame(String sessionId, WebSocketFrame frame) {
        if (frame.isBinary() || frame.isText()) {
            onMessage(sessionId, ByteBuffer.wrap(frame.binaryData().getBytes()));
        } else if (frame.isPing()) {
            WsonrpcPingListener listener = pingListener;
            if (listener != null) {
                listener.onPing(sessionId, frame.binaryData().getBytes());
            }
        }
    }

    protected void onMessage(String sessionId, ByteBuffer buffer) {
        serverBase.onMessage(sessionId, buffer);
    }

    protected void onClose(String sessionId) {
        serverBase.onClose(sessionId);
    }

    protected String sessionId(ServerWebSocket webSocket) {
        return webSocket.binaryHandlerID();
    }

    /**
     * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
     */
    private class ServerWebSocketSessionAdapter implements WebSocketSession {
        
        private final ServerWebSocket webSocket;
        private boolean close;
    
        ServerWebSocketSessionAdapter(ServerWebSocket webSocket) {
            this.webSocket = webSocket;
            this.close = false;
        }
    
        @Override
        public String getId() {
            return sessionId(webSocket);
        }
    
        @Override
        public boolean isOpen() {
            return !close;
        }
    
        @Override
        public void ping(byte[] bytes) throws IOException {
            WebSocketFrame frame;
            if (bytes == null || bytes.length == 0) {
                frame = PING_FRAME;
            } else {
                frame = WebSocketFrame.pingFrame(Buffer.buffer(bytes));
            }
            webSocket.writeFrame(frame);
        }
    
        @Override
        public void close() throws IOException {
            close = true;
            webSocket.end();
        }
    
        @Override
        public void sendBinary(byte[] bytes) {
            webSocket.writeFrame(WebSocketFrame.binaryFrame(Buffer.buffer(bytes), true));
        }
    }
}
